/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _TDOM_H_
#define _TDOM_H_

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define TDOM_IS_SPACE(a)    ((a) == ' ' || (a) == '\t')
#define TDOM_IS_CRLF(a)     ((a) == '\n' || (a) == '\r')

struct tdom_parser;

enum tdom_node_type {
    TDOM_NONE = 0,
    TDOM_ELEM,
    TDOM_ATTR,
    TDOM_TEXT,
};

struct tdom_node;
struct tdom_elem;
struct tdom_attr;
struct tdom_text;
struct tdom_ns;

TAILQ_HEAD(tdom_node_list, tdom_node);
TAILQ_HEAD(tdom_ns_list, tdom_ns);

struct tdom_doc {
    char *version;
    char *encoding;
    struct tdom_elem *root;
};

struct tdom_node {
    enum tdom_node_type type;
    struct tdom_elem *parent;
    TAILQ_ENTRY(tdom_node) sibling;
    char *name;
};

struct tdom_elem {
    struct tdom_node node;
    struct tdom_node_list children;
    struct tdom_node_list attrs;
    struct tdom_ns_list nss;
};

struct tdom_attr {
    struct tdom_node node;
    char *value;
};

struct tdom_ns {
    char *prefix;
    char *uri;
    TAILQ_ENTRY(tdom_ns) sibling;
};

struct tdom_text {
    struct tdom_node node;
    char *value;
};

/* constructors */
struct tdom_doc *tdom_doc_new(struct tdom_elem *root,
                              const char *version,
                              const char *encoding);
struct tdom_elem *tdom_elem_new(struct tdom_elem *parent,
                                const char *name);
struct tdom_attr *tdom_attr_new(struct tdom_elem *parent,
                                const char *name,
                                const char *value);
struct tdom_text *tdom_text_new(struct tdom_elem *parent,
                                const char *value);
struct tdom_ns *tdom_ns_new(const char *prefix,
                            const char *uri);

/* destructors */
void tdom_doc_delete(struct tdom_doc *doc);
void tdom_node_delete(struct tdom_node *node);
void tdom_elem_delete(struct tdom_elem *elem);
void tdom_attr_delete(struct tdom_attr *attr);
void tdom_text_delete(struct tdom_text *text);
void tdom_ns_delete(struct tdom_ns *ns);

/**
 * Sets the document parameters.
 *
 * @param[in] doc      target ducment instance.
 * @param[in] version  xml version.
 * @param[in] encoding character encoding.
 *
 * @retval  0 success
 * @retval -1 invalid parameter
 * @retval -2 memory error
 */
int tdom_doc_set(struct tdom_doc *doc,
                 const char *version,
                 const char *encoding);

/**
 * Adds the node to the end of the list of children of the element.
 *
 * @param[in] elem  the element to be added.
 * @param[in] child the node to add.
 */
void tdom_append_child(struct tdom_elem *elem,
                       struct tdom_node *child);

/**
 * Inserts the node after the existing node.
 * If ref is NULL, inserts child at the end of the list of children.
 *
 * @param[in] elem  the element to be added.
 * @param[in] child the node to add.
 * @param[in] ref   reference node.
 */
void tdom_insert_after(struct tdom_elem *elem,
                       struct tdom_node *child,
                       struct tdom_node *ref);

/**
 * Removes the child node from the list of children.
 *
 * @param[in] elem  the element to be removed.
 * @param[in] child the node to remove.
 */
void tdom_remove_child(struct tdom_elem *elem,
                       struct tdom_node *child);

/**
 * Adds or updates the first text node value of the specified named element.
 *
 * @param[in] elem   the element to add or update
 * @param[in] name   the name of the element
 * @param[in] value  the value of the text node to be added or updated
 * @param[in] ns_uri the namespace uri of the element
 *
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 * @retval -3 too many namespaces
 */
int tdom_set_elem_value(struct tdom_elem *elem,
                        const char *name,
                        const char *value,
                        const char *ns_uri);

/**
 * Retrieves the value of the first text element.
 *
 * @param[in] elem   the element to add or update
 * @param[in] name   the name of the element
 * @param[in] ns_uri the namespace uri of the element
 *
 * @return the value of the text node, or NULL when the element does not exist.
 */
const char *tdom_get_attr(const struct tdom_elem *elem,
                          const char *name,
                          const char *ns_uri);
/**
 * Adds or updates the specified attribute of the element.
 *
 * @param[in] elem   the element to add or update
 * @param[in] name   the name of the attribute
 * @param[in] value  the value of the attribute
 * @param[in] ns_uri the namespace uri of the attribute
 *
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 * @retval -3 too many namespaces
 */
int tdom_set_attr(struct tdom_elem *elem,
                  const char *name,
                  const char *value,
                  const char *ns_uri);

/**
 * Removes the specified attribute of the element.
 *
 * @param[in] elem   target element
 * @param[in] name   the name of the attribute to be removed
 * @param[in] ns_uri the namespace uri of the attribute
 */
void tdom_remove_attr(struct tdom_elem *elem,
                      const char *name,
                      const char *ns_uri);

/**
 * Adds the specified namespace. This method does not update the namespace
 * when the specified prefix already exists in the element.
 *
 * @param[in] elem   target element
 * @param[in] prefix the prefix to be added
 * @param[in] uri    the namespace uri to be added
 *
 * @retval  1 the specified prefix already exists in the element
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 */
int tdom_set_ns(struct tdom_elem *elem,
                const char *prefix,
                const char *uri);

/**
 * Returns the child element at the specified index.
 *
 * @param[in] elem       target element
 * @param[in] name       the name of the child element
 * @param[in] ns_uri     the namespace uri of the child element
 * @param[in] elem_index the index of the child element
 *
 * @return the child element, or NULL.
 */
struct tdom_elem *tdom_get_elem(struct tdom_elem *elem,
                                const char *name,
                                const char *ns_uri,
                                int elem_index);

/**
 * Returns the number of the child elements.
 *
 * @param[in] elem   target element
 * @param[in] name   the name of the child element
 * @param[in] ns_uri the namespace uri of the child element
 *
 * @return the number of the child elements.
 */
int tdom_count_elem(const struct tdom_elem *elem,
                    const char *name,
                    const char *ns_uri);

/**
 * Returns the value of the first child text node.
 *
 * @param[in] elem the element
 *
 * @return the element value
 */
const char *tdom_get_value(const struct tdom_elem *elem);

/**
 * Adds or updates the element value.
 *
 * @param[in] elem  target element
 * @param[in] value the value to be set
 *
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 */
int tdom_set_value(struct tdom_elem *elem, const char *value);

/**
 * Finds the namespace uri corresponding to the specified prefix.
 *
 * @param[in] elem   target element
 * @param[in] prefix a namespace prefix which might not be 0-terminated
 * @param[in] len    the length of prefix
 *
 * @return the corresponding namespace uri, or NULL when not found.
 */
const char *tdom_lookup_ns_uri(const struct tdom_elem *elem,
                               const char *prefix,
                               int len);

/**
 * Finds the namespace prefix corresponds to the specified uri at the specified
 * index. The number of corresponding prefix can be obtained from
 * tdom_count_ns_prefix().
 *
 * @see tdom_count_ns_prefix()
 *
 * @param[in] elem     target element
 * @param[in] uri      namespace uri
 * @param[in] ns_index index of prefix
 *
 * @return the corresponding prefix found at the index, or NULL when not found.
 */
const char *tdom_lookup_ns_prefix(const struct tdom_elem *elem,
                                  const char *uri,
                                  int ns_index);

/**
 * Checks if the specified uri is the default namespace or not.
 *
 * @param[in] elem target element
 * @param[in] uri  a namespace uri to be checked
 *
 * @retval 1 default namespace
 * @retval 0 not default
 */
int tdom_is_ns_default(const struct tdom_elem *elem, const char *uri);

/**
 * Returns the number of the prefix corresponds to the specified uri.
 *
 * @see tdom_lookup_ns_prefix()
 *
 * @param[in] elem target element
 * @param[in] uri  a namespace uri to be checked
 *
 * @return the number of the prefix.
 */
int tdom_count_ns_prefix(const struct tdom_elem *elem, const char *uri);

/* parser */
/**
 * Creates a new parser instance.
 * Caller must delete this instance by calling tdom_parser_delete() after use.
 *
 * @return a new parser instance.
 */
struct tdom_parser *tdom_parser_new();

/**
 * Destroys the specified instance.
 *
 * @param[in] parser an instance to be deleted. This parameter can be NULL.
 */
void tdom_parser_delete(struct tdom_parser *parser);

/**
 * Parses some input buffer. After finished parsing, the dom tree can be
 * retrieved by tdom_parser_get_doc().
 *
 * @param[in] parser parser instance
 * @param[in] s      pointer to buffer
 * @param[in] len    length of the buffer
 *
 * @retval  1 parse finished
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 * @retval -3 format error
 */
int tdom_parser_parse(struct tdom_parser *parser, const char *s, int len);

/**
 * Retrieves the dom tree. Caller must delete the returned instance by calling
 * tdom_doc_delete() after use.
 *
 * @param[in] parser parser instance
 *
 * @return the dom tree, or NULL when the parser has no available dom tree.
 */
struct tdom_doc *tdom_parser_get_doc(struct tdom_parser *parser);

/**
 * Skips head and tail spaces and returns the skipped buffer and length.
 *
 * @param[in]    s   buffer to parse
 * @param[inout] len pointer to the length of the buffer
 *
 * @return the pointer to the buffer, or NULL when the all data in buffer are
 *         spaces.
 */
const char *ignore_space(const char *s, int *len);

/**
 * Skips head spaces and returns the skipped buffer and length.
 *
 * @param[in]    s   buffer to parse
 * @param[inout] len pointer to the length of the buffer
 *
 * @return the pointer to the buffer, or NULL when the all data in buffer are
 *         spaces.
 */
const char *skip_head_space(const char *s, int *len);

/**
 * Skips head spaces and returns the skipped buffer and length.
 *
 * @param[in]    s   buffer to parse
 * @param[inout] len pointer to the length of the buffer
 *
 * @return the pointer to the buffer, or NULL when the all data in buffer are
 *         spaces.
 */
const char *skip_tail_space(const char *s, int *len);

/**
 * Cuts tail spaces and compacts the allocated memory.
 *
 * @param[in] text target text node (can be NULL)
 *
 * @retval  1 text is NULL
 * @retval  0 success
 * @retval -2 memory error
 */
int cut_tail_space(struct tdom_text *text);

/**
 * Counts the serialized length after escaping the specified value.
 *
 * @param[in] value a string before escape. This parameter may not be 0-terminated.
 * @param[in] len   length of the value to be counted
 *
 * @return the length after excaping the specified value.
 */
int tdom_count_escaped_len(const char *value, int len);

/**
 * Escapes the specified value.
 *
 * @param[in]    value  a string to be escaped. this paramter may not be 0-terminated.
 * @param[in]    len    length of the value
 * @param[in]    buffer pointer to the output buffer
 * @param[inout] size   the size of the buffer as input, the length of written
 *                      string as output.
 *
 * @return the read size of input value.
 */
int tdom_escape(const char *value, int len, char *buffer, int *size);

/**
 * Unscapes the specified value in place.
 *
 * @param[in]    value pointer to a buffer
 * @param[inout] len   length of the buffer to be parsed as an input.
 *                     length of the unescaped string as an output.
 *
 * @return the length that is in the middle of unescaping. 0 means all data has
 *         been unescaped.
 */
int tdom_unescape(char *value, int *len);

#define TDOM_SOAP_ENV_NS_URI "http://schemas.xmlsoap.org/soap/envelope/"
#define TDOM_SOAP_ENC_NS_URI "http://schemas.xmlsoap.org/soap/encoding/"

/**
 * Creates a new soap template.
 *
 * @param[in] service_type a service type
 * @param[in] action       an action name
 *
 * @return a new soap instance, or NULL when some invalid parameters were
 *         specified or when memory allocation failed.
 */
struct tdom_doc *tdom_soap_new(const char *service_type,
                               const char *action);

/**
 * Adds or updates a soap argument.
 * The specified value is copied into the internal dom node.
 *
 * @param[in] doc     target soap document
 * @param[in] argname name of the argument
 * @param[in] value   argument value
 *
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 */
int tdom_soap_set(struct tdom_doc *doc, const char *argname, const char *value);

/**
 * Adds or updates a soap argument with no copy.
 * The specified value is set to the internal dom node.
 *
 * @param[in] doc     target soap document
 * @param[in] argname name of the argument
 * @param[in] value   pointer to the argument value
 *
 * @retval  0 success
 * @retval -1 invalid argument
 * @retval -2 memory error
 */
int tdom_soap_set_pointer(struct tdom_doc *doc, const char *argname, char *value);

/**
 * Retrieves an argument value.
 *
 * @param[in] doc     target soap document
 * @param[in] argname name of the argument
 *
 * @return the argument value, or NULL when the specified argument does not exist.
 */
const char *tdom_soap_get(const struct tdom_doc *doc, const char *argname);

#define TDOM_NS_UPNP_CONTROL  "urn:schemas-upnp-org:control-1-0"

/**
 * Creates a new soap error.
 *
 * @param[in] error_code   error code
 * @param[in] error_string error description
 *
 * @return a new soap error instance, or NULL when memory allocation failed.
 */
struct tdom_doc *tdom_soap_err_new(int error_code, const char *error_string);

/**
 * Retrieves an error information.
 *
 * @param[in] doc  target soap document
 * @param[in] name name of the field
 *
 * @return the error information, or NULL when the specified field does not exist.
 */
const char *tdom_soap_get_upnp_err(const struct tdom_doc *doc, const char *name);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* _TDOM_H_ */

